home *** CD-ROM | disk | FTP | other *** search
Text File | 1992-07-23 | 42.1 KB | 1,256 lines |
- COP is shareware meaning try before you buy. To use
- COP for any other purpose than for evaluation to
- arrive at a purchase decision requires that you
- register COP. The fee is $35 US. Upon registration
- you will be sent the latest version on 3.5 DOS formated
- diskette and hard copy documentation.
-
- COP
-
- The C Object Programming Style Manual
-
- (c) Copyright John W. Small 1992
- All rights reserved
-
- PSW / Power Software
- P.O. Box 10072
- McLean, VA 22102 8072 USA
- (703) 759-3838
-
-
- Introduction
-
- The COP (C Object Programing) style manual prescribes a
- systematic, formalized approach to OOP (Object Oriented
- Programming) in C. COP supports the concepts of
- encapsulation, single and multiple inheritance
- including virtual base classes, and polymorphism using
- the C preprocessor. Simply include the "cop.h" header
- file in your source file and you are ready to begin
- object orient programming in C.
-
- Please keep in mind that COP is not intended as a
- replacement for C++. Rather its purpose is to present
- an OOP conceptual platform to programmers forced by
- project requirements to code in C. The complexities of
- rendering an OOP design in a non-OOP language such as C
- are often insurmountable. But with a uniform,
- documented, and well understood style of incorporating
- OOP concepts, the proposition becomes tenable. It is
- hoped that COP will help OOP concepts to find their way
- into the C world and perhaps thereby hasten the
- acceptance of C++ and OOP.
-
- By applying COP's OOP implementation strategy to
- assembly language we could just as well have had ALOP
- (Assembly Language Object Programming). Learning COP
- strategy will indeed help you conceptualize the
- implementation strategies of other OOP languages. In
- fact it could very well start you on your way to
- implementing your own OOP language.
-
-
- What is OOP (Object Orient Programming)?
-
- OOP makes code more readily reusable and extensible
- through the use of objects. These objects are used to
- describe real world objects lending an intuitive
- approach to application design. An object has an
- internal state (data) and can react (behavior) to
- simulus (message functions). OOP changes the focus of
- application design from functional decomposition to
- objective behavioral analysis. The former entails flow
- charting or pseudo code while the latter entails object
- identification and classification. Historically,
- advances in algorithmic (procedural) languages have
- attempted to make machine abstracted syntheses of the
- real world progressively less arcane. OOP takes the
- opposite approach of attempting to take real world
- objects and respresent them on the machine. OOP
- languages should facilitate iterative design;
- unfortuanately this is where C++ starts to fall down
- without the aid of CASE tools, and COP of course fails
- miserably. Never the less, COP bridges a the gap
- between the procedural C world and the OOP revelation
- of the C++ world.
-
- OOP embodies the concepts of encapsulation,
- inheritance, and polymorphism. Encapsulation means
- packaging data and its associated functions together in
- a object (structure) thus controlling access and
- enforcing protocol. Objects can inherit (hierarchical)
- the properties (data and functions) of other objects
- thus allowing for the reuse of code in an extensible
- fashion. Objects in similar (polymorphic) behaviorial
- categories (hierarchies) but with differing attributes
- can be distinguished at run time via the process of run
- time (late) binding of virtual functions (function
- pointers) thereby encouraging highly abstracted coding
- methods which save development time (only in the sense
- of the overall picture), reduce code sizes and
- maintenance efforts. Undoubtly you have been using
- some OOP techniques in your C coding all along without
- really thinking about it in such a formalized fashion,
- much like the early pioneer programmers started using a
- structured flow regimen to eliminate spagetti code long
- before formalized structure programming languages were
- written. After structure programming came data
- structures and now the time has come to move on to
- objects and OOP.
-
-
- Getting Started with COP
-
- Since COP is really only a style of programming, a
- header file, "cop.h", containing macros is all that's
- necessary - there is no "cop.c" source file. However,
- boiler plate forms have been provided to help you
- cookbook your own objects. These are the cop.hfm and
- cop.cfm files. For example, let's say you want to
- code an object called shape. Copy cop.hfm to
- shape.h and cop.cfm to shape.c and edit these new
- files. These files are extensively commented to
- refresh your understanding of COP style rules outlined
- in this manual.
-
- The easiest way to learn the COP style is to see OOP
- concepts implemented in C++ and compare it to the
- corresponding COP code. If you don't known C++ you'll
- learn some along the way!
-
-
-
- Automatic Typedef's
-
-
- In C++ a structure tag becomes the name of the
- structure type just as if it had been typedef'ed, e.g.
-
-
- struct MyStruct { ... };
-
- MyStruct foobar; // okay in C++ but not in C!
-
-
- COP automatically typedef's structure tags as follows:
-
- struct_(MyStruct) { ... };
-
- MyStruct foobar; /* COP's struct_() macro
- typedef'ed MyStruct
- automatically */
-
-
- As a side note, C++ has an alternate form for comments
- using the "//" to introduce comments that continue to
- the end of the current line.
-
-
-
- Encapsulation
-
-
- C++ allows the encapsulation of functions with the data
- they operate on to create objects. Object packaging is
- an important first step to modularity and reuseability.
-
-
- struct MyStruct {
- int x, y;
- int getx() { return x; }
- int gety() { return y; }
- void setx(int x);
- void sety(int y);
-
- };
-
- void MyStruct::setx(int x)
- { this->x = x; }
-
- void MyStruct::sety(int y)
- { this->y = y; }
-
-
- MyStruct foobar;
-
- foobar.setx(5);
- foobar.sety(10);
-
- // output foobar coordinates
-
- cout << "(" << foobar.getx() << ","
- << foobar.gety() << ")";
-
-
- Actually the functions aren't part of the structure but
- they tell the C++ compiler that they are part of the
- MyStruct interface. C++ member functions defined
- within the structure are said to be inline. When
- called, the compiler inserts their code inline instead
- of setting up a function call. Notice that out of line
- member functions are defined with the structure's name
- and the scope resolution operator, "::." This prevents
- confusion with same named member functions of different
- structures. You may be wondering where the "this" came
- from in the setx() function. All member functions have
- an implicit "this" pointer formal parameter that points
- to the structure that setx() is to operate on. It is
- only necessary in C++ to use the "this" pointer to
- resolve naming conflicts. The "x" formal parameter
- masks MyStruct.x so "this" was used to access it.
- Stream output looks strange to you if you're coming
- from a C background. You'll need to refer to a C++
- primer for more information.
-
-
- COP encapsulation style is as follows:
-
-
- struct_(MyStruct) {
- int x, y;
- };
-
- #define MS_getx(thiS) (1?(thiS)->x:0)
- #define MS_gety(thiS) (1?(thiS)->y:0)
-
- void MS_setx(MyStruct * thiS, x)
- { thiS->x = x; }
-
- void MS_sety(MyStruct * thiS, y)
- { thiS->y = y; }
-
-
- MyStruct foobar;
-
- MS_setx(&foobar,5);
- MS_sety(&foobar,10);
-
- /* output foobar coordinates */
-
- printf("(%d,%d)",MS_getx(&foobar),
- MS_gety(&foobar));
-
-
- As you know with C you can't place functions inside
- structures nor can you have two functions with the same
- name. So COP style has you contract a structure's name
- into a member function prefix of your choice that is
- then prepended to member function names thereby
- avoiding naming conflicts. In order to simulate inline
- functions, macros are of course used. COP style has
- you use the condition assignment statement, (1?
- (thiS)->x:0), etc., to guarantee that you don't have an
- lvalue that can be assigned to! Out of line member
- function definitions appear as you would expect them.
- Of course you have to supply the implicit "thiS"
- pointer since the C compiler isn't going to do it for
- you like the C++ compiler does. You may be wondering
- why "thiS" has a capitalized "S". It is COP style to
- capitalize the last letter of names to indicate that
- they are pointers. Also some combination C/C++
- compilers get upset if you're compiling in C and have a
- variable named "this." (I won't mention any names!)
-
-
-
- Information Hiding
-
-
- Besides the hiding of static variables inside the files
- in which they occur, C++ has a special structure type
- called "class" in which all members, both data and
- functions, are private unless otherwise specified.
- The following excerpt from shape.cpp demonstrates:
-
-
- class Shape {
- unsigned x, y;
- public:
- ...
- unsigned getx() { return x; }
- unsigned gety() { return y; }
- ...
- };
-
-
- Here, x and y are private and the compiler will only
- let member functions access them.
-
- COP has only the honor system as shown before:
-
- struct_(Shape) {
- /* private: */
- unsigned x, y;
- ...
- };
-
- #define SH_getx(thiS) (1?(thiS)->x:0)
- #define SH_gety(thiS) (1?(thiS)->y:0)
-
-
- Polymorphism
-
- C++ allows you to specify the run time binding of
- functions. Perhaps you have seen or written a sort
- routine that takes a compare function pointer as an
- argument. The qsort() function prototype in the
- ANSI C stdlib.h file is a perfect example. The idea
- here is that the sort function doesn't know until run
- time what compare function it will call. This allowed
- you to write a generic sort function that is extensible
- to operate on any data type. The sort routine is said
- to be polymorphic in that it can take on many forms
- (of data) in a rather homogeneous behavioral pattern
- (that of sorting).
-
- Let's expand this idea to objects (struct or class)
- with member functions. Suppose you wanted to write a
- graphics display library. It would be nice if you
- could write a generic shape structure with a member
- function that could be called to display whatever shape
- it happens to take on. But the member function would
- really have to be a pointer since the data type that
- is to be displayed isn't yet known. In C++ this
- special type of member function is identified with the
- keyword "virtual."
-
-
- class Shape {
- unsigned x, y;
- public:
- ...
- unsigned getx() { return x; }
- unsigned gety() { return y; }
- virtual void show(int xxpose = 0,
- int yxpose = 0,
- unsigned scale = 1) {}
- ...
- };
-
-
- The show() virtual function does nothing as inline
- coded here. After all it doesn't know what kind of
- shape it is. The C++ compiler uses this information
- to maintain an internal virtual function pointer table.
- The assignments used in the formal parameter list of
- show() are default values. If show() is invoked with
- no actual parameters the C++ compiler will
- automatically supplies 0 for both x and y and 1 for
- scale.
-
-
- You guessed it, With COP you have to maintain the
- virtual function tables yourself.
-
-
- /* shape.h */
-
- struct_vFt_forward(Shape);
-
- struct_(Shape) {
- /* private: */
- unsigned x, y;
- polymorphic();
- vFT_decl(Shape);
- };
-
- struct_vFt(Shape) {
- vF_decl(void,show,
- (Shape * thiS, int xxpose,
- int yxpose, unsigned scale));
- ...
- };
-
- vFt_decl(Shape,Shape);
-
- vf_decl(void,SH,SH,show,(Shape * thiS,
- int xxpose, int yxpose,
- unsigned scale));
-
-
- /* shape.c */
-
- vFt_def(Shape,Shape) = {
- vF_value(SH,SH,show),
- ...
- };
-
- vf_def(void,SH,SH,show,(Shape * thiS,
- int xxpose, int yxpose,
- unsigned scale)) {}
-
-
- The vFT_decl() macro must be included in any structure
- that introduces virtual member functions. This macro
- expands to point to the table of virtual function
- pointers for Shape. Of course this pointer type is
- unknown unless we forward declare it with the
- struct_vFt_forward() macro! As you will see later,
- any structure that introduces or inherits virtual
- member functions is polymorphic and must therefore
- include the polymorphic() macro.
-
- The virtual function table structure must be laid out
- also using the struct_vFt() macro. Use the vF_decl()
- macro to declare the function pointers themselves
- within the table. You can always refer to the macros
- in cop.h to see what the parameters are. The first
- parameter for vF_decl() is the virtual function's
- return type. The second is the function's name, and
- the third is its parenthesized formal parameter list.
- Note again that you must supply the "thiS" pointer as
- the first parameter in this list!
-
- Next, the default virtual function table for Shape is
- is declared using the vFt_decl() macro. Its two
- parameters are the overriding and spawning structures.
- Since this is the default table Shape is the overriding
- as well as the spawning structure. The spawning
- structure is the structure that introduces the virtual
- member function while the overriding structure is the
- one that redefines the function to be meaningful for
- itself.
-
- Don't forget you must also declare the default virtual
- function itself using the vf_decl() macro. The shape.c
- file must naturally include the virtual function table
- and virtual function definitions, i.e. vFt_def() and
- vf_def()! Make a mental note of the various COP
- macros used here. A capitalized last letter in an
- identifier name indicates a pointer to the type
- identified without the last letter capitalized. For
- example, vF_decl() is a virtual function pointer
- declaration while vf_def() is the virtual function
- definition itself. If vFt_decl() is the declaration
- for a virtual function pointer table, what does
- vFT_decl() signify? You got it, a pointer to the table
- of virtual function pointers.
-
- In memory a vFt looks something like:
-
-
- instance virtual function
- table
-
- ----------------- --------------------
- | | | |
- | structType | | structType_vFt |
- | | | |
- | (vFT)|-------->| (vF)-> |
- | (descendanT)|_ | |
- ----------------- \ --------------------
- |
- _|_
- \|/
- ,
-
-
- The pointer to the virtual function table is never
- NULL and thus not checked! Likewise the virtual
- function pointers in the table themselves are never
- NULL and not checked. This of course requires that
- you code your constructors/destructor as outline in
- cop.cfm!
-
- The descendanT pointer is used by overriding vf's to
- locate derived structure components. Any overriding
- vf must have the logic for traversing descendant
- chains. All derived structures are necessarily
- polymorphic and must include the polymorphic() macro
- even if they don't introduce new vf's at their level.
-
-
- Wow! What a chunck of code is required for COP
- virtual functions that's done automatically by C++!
- Please, before you give up on COP consider this. If
- you have to port a C++ OOPs designed program to C, COP
- can be a real lifesaver! It took me nearly six months
- to devise and refine COP to its present form which can
- implement any AT&T C++ 3.0 semantic form except
- exceptions! If you have been putting off learning C++,
- by the time you finish this shape example you should be
- convinced of C++'s superior conceptual plane and how it
- makes life easier for the programmer in the long run.
- But even if you only code in C++, COP will make you
- have greater appreciation for what the C++ compiler is
- doing behind the scenes for you.
-
-
-
- Automatic Initialization and Cleanup
-
-
- Before we can see how polymorphism and virtual
- functions work in practice, we need to first touch on
- the C++ concept of initialization and cleanup. The
- idea in C++ is that data should be initialized upon
- definition and cleaned up when no longer used. Further
- more these processes should be automated and not burden
- the programmer. This entails the use of special
- member functions known as constructors and destructors.
-
-
- class Shape {
- unsigned x, y;
- public:
- Shape(unsigned x = 0, unsigned y = 0)
- { this->x = x; this->y = y; }
- unsigned getx() { return x; }
- unsigned gety() { return y; }
- virtual void show(int xxpose = 0,
- int yxpose = 0,
- unsigned scale = 1) {}
- virtual ~Shape() {}
- };
-
-
- The constructor member function has the same name as
- its associated class. The destructor likewise has the
- same name with a ~ (tilda) prefix. Notice that neither
- constructors nor destructors have a return types!
- Shape's constructor simply assigns x and y values.
- Since Shape has no pointers to dynamically allocated
- memory, etc., there is nothing for the destructor to
- do. Shape's destructor here is defined as virtual
- because some specific Shapes may have to be destructed
- which can be accomplished by overriding this
- destructor's definition. The correct destruct function
- is then bound at run time for the particular Shape at
- hand.
-
-
- Before proceeding with COP constructors/destructors,
- let's see how inheritance works in C++. We're going to
- define a Circle which IS A Shape but we must also
- overriding Shape::show() redefining it to display a
- Circle.
-
-
- class Circle : public Shape {
- protected:
- unsigned radius;
- public:
- Circle(unsigned radius
- = (unsigned) getmaxy()/8,
- unsigned x = 0, unsigned y = 0)
- : Shape((x?x:radius),
- (y?y:radius))
- { this->radius = radius; }
- virtual void show(int xxpose = 0,
- int yxpose = 0,
- unsigned scale = 1)
- {
- circle((int)getx()+xxpose,
- (int)gety()+yxpose,
- (int)(radius*scale));
- }
- };
-
-
- We do this by defining the class Circle which is
- derived publicly from the Shape class. Being derived
- means that Circle inherits Shape's x and y variables
- and Shape's getx() and gety() functions. Being
- publicly derived from shape means that any public
- member of Shape is a public member of Circle, data or
- function it doesn't matter. To Shape's x and y data,
- Circle adds radius in protected scope. Protected means
- that any shape derived from Circle can access radius
- but other users of the Circle class can not. In the
- shapes example PieSlice inherits Circle and is able
- to directly read its Circle inherited radius member.
- Even though Circle inherits Shape's x and y members it
- can't access them directly because they are private
- to Shape. But the inherited getx() and gety() are
- public so Circle simply uses them to read the x and y
- values. This scope hiding in C++ is very powerful in
- that by controlling access interfaces, coupling can be
- defined in a precise modular fashion.
-
- The Circle constructor adds radius to its formal
- parameter list with a default of getmaxy()/8, a BGI
- function. The Circle constructor must first call the
- Shape constructor to initialize its Shape inherited
- portions. Notice that if the defaults for x and y are
- used, namely zero, that x and y are supplied values of
- radius. This is to insure a default constructed Circle
- is entirely visible. The Circle constructor itself
- only has to initialize radius.
-
- Circle overrides Shape::show() with its own definition.
- It simply reads its x and y location and transposes it
- by xxpose and yxpose respectly and scales it in a call
- to the BGI circle primitive.
-
-
- Without having to see the declaration for PieSlice we
- can still understand what's happening in the following
- code. We are creating two Shapes and then displaying
- them:
-
-
- Shape * S1 = new Circle();
- Shape * S2 = new PieSlice();
-
- S1->show();
- S2->show();
-
-
- The S1 shape calls Circle's show() to display the
- circle. The show() virtual function is run time bound
- to Circle::show(). S2's show() is run time bound to
- PieSlice::show() instead. This polymorphic behavior
- allows us to write code to display a collection of
- shapes without having to know what those shapes are in
- advance just like we could write a sort for unknown
- data. C++ maintains the overriding virtual function
- tables automatically.
-
-
- As usual, COP is a pain but with gain! You have to
- code constructors and destructors yourself. There is
- quite a bit more going on behind the scenes that the
- C++ compiler is doing for you automatically which
- you'll soon see. But take heart, this is the last
- tough section of COP and then things will start to come
- together!
-
-
- struct_vFt_forward(Shape);
-
- struct_(Shape) {
- /* private: */
- unsigned x, y;
- polymorphic();
- vFT_decl(Shape);
- };
-
- struct_vFt(Shape) {
- vF_decl(void,show,
- (Shape * thiS, int xxpose,
- int yxpose, unsigned scale));
- vF_decl(void,destruct,(Shape * thiS,
- unsigned nobj, int malloced));
- };
-
- vFt_decl(Shape,Shape);
-
- vf_decl(void,SH,SH,show,(Shape * thiS,
- int xxpose, int yxpose,
- unsigned scale));
- vf_decl(void,SH,SH,destruct,(Shape * thiS,
- unsigned nobj, int malloced));
-
-
- struct_initVFTs_decl(SH,(Shape * thiS,
- void * descendanT_0,
- vFT_0_decl(Shape)));
-
- #define SH_malloc(nobj) \
- MALLOC(sizeof(Shape)*nobj)
- #define SH_free(thiS) FREE(thiS)
-
-
- struct_init_decl(Shape,SH,_,
- (Shape * thiS_0, unsigned nobj,
- unsigned x, unsigned y));
-
- struct_destruct_decl(Shape,SH);
-
-
- #define SH_init(thiS_0,nobj,x,y) \
- struct_init(SH,_,(thiS_0,nobj,x,y))
- #define SH_init_default(thiS_0) \
- SH_init(thiS_0,1,0,0)
- #define SH_new(nobj,x,y) \
- SH_init((Shape *)0,nobj,x,y)
- #define SH_new_default() \
- SH_init_default((Shape *)0)
- #define SH_destruct(thiS,nobj) \
- vf_runTimeBind(thiS,destruct, \
- (thiS,nobj,0))
- #define SH_delete(thiS_0,nobj) \
- if (thiS_0) vf_runTimeBind(thiS_0, \
- destruct,(thiS_0,nobj,1)); else
-
- #define SH_getx(thiS) (1?(thiS)->x:0)
- #define SH_gety(thiS) (1?(thiS)->y:0)
-
- #define SH_show(thiS,xxpose,yxpose,scale) \
- vf_runTimeBind(thiS,show, \
- (thiS,xxpose,yxpose,scale))
- #define SH_show_default(thiS) \
- SH_show(thiS,0,0,1)
-
-
- Okay the first thing new in this listing is the
- virtual destructor function pointer declaration in the
- virtual function table structure. You must always
- follow this form exactly for virtual destructors! C++
- allows you to call a constructor and destructor
- repeatedly for each cell of a vector of class
- instances automatically. With COP you have to tell it
- how many (nobj) instances there are! Likewise you must
- tell the destructor whether the instance(s) need to be
- freed via malloced.
-
- The next thing suprising but then immediately obvious
- is the need to initialize the virtual function tables.
- The struct_initVFTs_decl() macro declares the function
- that does this job.
-
- The SH_malloc() and SH_free() macros are used inside
- the constructor/destructor definitions which you'll see
- in a minute. These macros allow you to redefine the
- heap manager on a structure by structure basis. The
- MALLOC and FREE macros, defined in cop.h allow to
- redefine the heap manager on a global basis. These
- macros correspond to C++ concept of overloading the
- new and delete operators, C++'s equivalent to malloc()
- and free().
-
- The struct_init_decl() and struct_destruct_decl()
- macros define the protected scope constructor and
- destructor for shape. The "_" parameter allows for
- overloading constructors by allowing various naming
- suffixes. There is no C++ equivalent of these
- protected scope constructor/destructors. COP uses
- these to define a consistent, extensible base line.
- Notice that the constructor's parameter list has a
- "thiS_0" parameter. This means that the "thiS"
- parameter may be passed a NULL value. The COP style
- doesn't require a member function to check for NULL
- unless it has a "thiS_0" formal parameter! The reason
- for this is efficiency, especially inline macros. When
- "thiS_0" is NULL in a call to the constructor, it
- signals the constructor that the instance being
- initialized must first be dynamically allocated.
- Remember the destructor is signaled to free an instance
- by the malloced parameter.
-
- Next, a series of public constructor/destructor macros
- defines the user interface to Shape. The only way
- for COP to allow for default parameters is to code
- additional macros with default suffixes whose
- definitions supply those defaults. Notice that the
- SH_destruct() public destructor doesn't free the
- instance but SH_delete() does.
-
- The SH_show() macro run time binds the virtual function
- for you.
-
-
- Before we look at the source definitions for Shape's
- member functions we need to consider how COP copes
- with virtual function table initialization in an
- extensible fashion. All this is handled for you
- automatically by the C++ compiler and if you are
- already a C++ programmer chances are you never really
- thought about how the vFTs were handled.
-
-
- The ordering and internal steps of constructor and
- destructor calls are always coded as follows to insure
- extensibility:
-
-
-
- base struct
- ---------- -------------- --------------
- | | 2 | | 1 | |
- | init |----->| initVFTs |<-----| destruct |
- | | | | | |
- | 3 data | | | | 2 data |
- ---------- -------------- _--------------
- /|\ \ /|\ /| /|\
- | \__________|___________/ |
- | 1 | | 3
- derive|d struct | |
- ---------- -------------- --------------
- | | 2 | | 1 | |
- | init |----->| initVFTs |<-----| destruct |
- | | | | | |
- | 3 data | | | | 2 data |
- ---------- -------------- _--------------
- /|\ \ /|\ /| /|\
- | \__________|___________/ |
- | 1 | | 3
- | | |
- ---------- -------------- --------------
- | | 2 | | 1 | |
- | init |----->| initVFTs |<-----| destruct |
- | | | | | |
- | 3 data | | | | 2 data |
- ---------- -------------- _--------------
- \ /|
- \______________________/
-
-
-
-
- To read the chart imagine the base struct is Shape.
- The intermediate derived struct is then Circle and
- the lowest is PieSlice. When PieSlice::init()
- (constructor) is called it first calls its base class
- constructor, Circle::init(). After Circle::init()
- returns, PieSlice::init() calls PieSlice::initVFTs()
- to initialize the hierarchy's virtual function tables
- to read properly for the PieSlice level. Once that
- is done, PieSlice::init() initializes its data. You
- should be aware that PieSlice::initVFTs() calls its
- base class initVFTs(). Besides initializing vFT
- pointers, the descendanT pointer chain is also
- established. The show() virtual function that is bound
- at run time traverses this descendanT pointer chain to
- get to the derived data. The polymorphic() macro you
- saw earlier expands inserting descendanT pointers
- in the various structures. Later on you will see a
- diagram of structures with inherited structures.
-
- On the destructor side of the house,
- PieSlice::destruct() first calls initVFTs() to
- reinitialize the vFTs to the current PieSlice level
- defaults, then destructs the PieSlice level data and
- calls Circle::destruct() to destruct lower levels.
- Circle::destruct() reinitializes the vFTs to the Circle
- level. After all we don't want PieSlice vFs to be
- called after PieSlice data has since been destructed!
-
- Before choking on the next source listing, remember
- that cop.hfm and cop.cfm provide skeletal source forms
- which you can edit to greatly simplifing the task of
- generating the source you are about to consider. And
- now for Shape member function source definitions found
- in shape.c interspersed with additional comments:
-
-
- /* Shapes default vFt definition */
-
- vFt_def(Shape,Shape) = {
- vF_value(SH,SH,show),
- vF_value(SH,SH,destruct)
- };
-
-
- /* Shape::show() default definition */
-
- /* ARGSUSED */
- #pragma argsused
- vf_def(void,SH,SH,show,(Shape * thiS, int xxpose,
- int yxpose, unsigned scale))
- {}
-
-
- /* Shape::~Shape()'s protected virtual function
- The public destructor was defined before in
- the two macros, SH_destruct() and SH_delete().
- Notice the use of COP's base line protected
- constructors/destructor throughout the rest
- of this listing.
- */
-
- vf_def(void,SH,SH,destruct,(Shape * thiS,
- unsigned nobj, int malloced))
- {
- struct_destruct(SH,thiS,nobj,malloced);
- }
-
-
- /* Protected vFT initializer function which has no
- external C++ equivalent.
- */
-
- struct_initVFTs_def(SH,(Shape * thiS,
- void * descendanT_0, vFT_0_decl(Shape)))
- {
- /* Establish the descendanT chain, the last
- level has no descendanT!
- */
- poly_assign(thiS,descendanT_0);
-
- /* Since initVFTs() can be called by a
- derived class' initVFTs() it must be
- decided whether to assign the current
- level default vFT or a derived level's
- vFT.
- */
- vFT_assign(Shape,Shape,
- thiS,vFT_0_name(Shape));
- }
-
-
- /* This Shape::Shape() protected function is a COP
- base line constructor (no C++ equivalent). The
- public Shape::Shape() constructors were defined
- above in the SH_init() macro series.
-
- Referring back to the constructor/destructor
- call chart and the code below, notice that
- the struct_init() can be called either directly
- as a constructor for the instance at hand or as an
- intermediately constructor to initialize a base
- class. Therefore the code must be written to
- handle both situations. When called as an instance
- constructor, nobj will be >= 1 and thiS_0 may be
- NULL. But when called as an intermediate level
- constructor, nobj is always 1 and thiS_0 is never
- NULL! When nobj > 1, acting as an instance
- constructor it calls the intermediate level
- constructors repeatedly with nobj = 1 for each cell
- of the vector.
- */
-
- struct_init_def(Shape,SH,_,
- (Shape * thiS_0, unsigned nobj,
- unsigned x, unsigned y))
- {
- unsigned i;
-
- if (!nobj)
- return (Shape *)0;
-
- /* If thiS_0 == NULL this is an instance level
- constructor that needs to first allocate the
- instance at hand.
- */
- if (!thiS_0) if ((thiS_0 = (Shape *)
- SH_malloc(nobj))
- == (Shape *)0)
- return (Shape *)0;
- /*
- If nobj > 1 this is again an instance level
- constructor call for a vector of instances
- so loop through each cell calling its next lower
- intermediate level constructor on a cell by cell
- basis. After the next lower level returns
- reinitialize the vFT hierarchy for the current
- level and then initialize the data for the current
- level. Since Shape has no base classes the first
- step of calling base class constructors doesn't
- appear here.
- */
- for (i = 0; i < nobj; i++) {
- struct_initVFTs(SH,(&thiS_0[i],(void *)0,
- vFT0(Shape)));
- thiS_0[i].x = x;
- thiS_0[i].y = y;
-
- } /* for */
- return thiS_0;
- }
-
- /* This Shape::~Shape() protected function is a COP
- base line destructor (no C++ equivalent). Remember
- there is a protected virtual destructor definition
- of Shape::~Shape() above which calls this non
- virtual destructor. This COP base line function
- provides for a consistent destructor format
- regardless of whether or not the destructor is
- virtual. It also provides a consistent basis for
- overriding the protected virtual destructor (see
- cop.cfm structType virtual destructor comments).
-
- If you look at the macro definition for
- struct_destruct_def() in cop.h you will see to it
- expands into PT_SH_Shape_destruct(Shape * thiS_0,
- unsigned nobj, int malloced). The PT_ prefix means
- that the function is protected scope. PV_ can be
- used for private scope.
-
- Anyway, by again referring to the destructor
- hierarchy call diagram you can see this destructor
- first reinitializes the vFT hierarchy for the
- current level, destructs the data at the current
- level, and then calls the base class destructors.
- Shape has no data or base classes that need
- destructing so those steps have been left out.
-
- Like the constructor, nobj can only be > 1 and
- malloced != 0 in calls to instance level
- destructors. When called from a derived level,
- nobj is always equal to 1 and malloced is equal
- to 0! After all, we don't an intermediate level
- destructor freeing the instance!
- */
-
- struct_destruct_def(Shape,SH)
- {
- unsigned i;
-
- if (!thiS_0 || !nobj)
- return;
-
- for (i = nobj; i--; /* no reinit */) {
- struct_initVFTs(SH,(&thiS_0[i],
- (void *)0,vFT0(Shape)));
- }
-
- if (malloced)
- SH_free(thiS_0);
- }
-
-
- Two more shapes you need to know about in the shapes
- example are Rectangle and Segment. Rectangle is
- straight forward similar to Circle or PieSlice. A
- Segment is a collection of Shapes which is in a sense
- a Shape itself. The Segment C++ class declaration is:
-
-
- class Segment : public Shape {
- Shape ** shapes;
- unsigned shapeCount;
- public:
- Segment(unsigned x, unsigned y,
- unsigned shapeCount,
- .../* shapes */);
- // All shapes must be dynamically allocated!
- // Segment then owns these shapes for deletion
- // purposes.
- virtual void show(int xxpose = 0,
- int yxpose = 0,
- unsigned scale = 1);
- ~Segment();
- };
-
-
- Segment::show() calls Shape::show() for each of its
- internal shapes transposing them to the Segment's x,y
- coordinates and any additional transpose and scale.
- The Segment's destructor calls each of its subshape's
- destructor. Then it deletes its own internal shapes
- vector.
-
- You can view the COP source in shape.h and shape.c.
-
- Be sure to make a comparison study of the shapes
- example contrasing the C++ version with the C
- version. If you have the Borland C++ compiler go
- ahead and run the two versions. To compile:
-
- bcc shapes.cpp shape.cpp rectpie.cpp graphics.lib
-
- or
-
- bcc shapes.c shape.c rectpie.c graphics.lib
-
-
- You should take note that shape.c/pp has the Shape,
- Segment, and Circle declarations/definitions. The
- shapes.c/pp main program loop focuses processing around
- the Segment object. This illustrates another important
- feature of OOP. The rectpie.c/pp was able to extend
- the Shapes that could be processed within a Segment to
- include the Rectangle and PieSlice without recompiling
- the shape.c/pp file. This is an example of object
- extensibility. Normally with C, a tagged union
- structure with a case statement would be used to
- process various shapes. If new shapes had to be added
- the original case statement would have had to be
- modified and recompiled. This is not the case with the
- polymorphic object approach shown here with Segments!
- The reusability of the Segment code was greatly
- enhanced along with productivity in creating those
- extensions.
-
-
- An additional subtle feature of C++ is implicit type
- conversion. With COP there is no such thing as
- implicit. Consider the C++ excerpt from shapes.cpp,
- the main program loop, and the corresponding C excerpt
- from shapes.c:
-
-
- // shapes.cpp
-
- Segment s = Segment(0,0,3,
- new Circle(),
- new Rectangle(),
- new Segment(20,20,2,
- new PieSlice(),
- new Rectangle()
- )
- );
-
- s.show();
- s.show(getmaxx()/2, getmaxy()/2, 2);
-
-
- /* shapes.c */
-
- Segment s;
-
- (void) SG_init(
- (&s,1,0,0,3,
- CR_ShapeThiS_0(CR_new_default()),
- RT_ShapeThiS_0(RT_new_default()),
- SG_ShapeThiS_0(SG_init(
- ((Segment *)0,1,20,20,2,
- PS_ShapeThiS_0(PS_new_default()),
- RT_ShapeThiS_0(RT_new_default())
- )
- ))
- )
- );
-
- SG_show_default(&s);
- SG_show(&s,getmaxx()/2,getmaxy()/2,2);
-
-
- The C++ version implicitly typecasts the Circle,
- Rectangle, and Segment pointers to Shape pointers.
- The COP version has to call typecast functions to do
- the conversions. It's not as simple as just retyping
- a pointer. The pointer actually has to point to a
- different part of the object.
-
- It's time to look at what structures with inherited
- structures look like.
-
- Consider the following COP code for structure
- inheritance and the depicting diagram
- (vFTs not shown, note that base3 is virtual!):
-
-
- struct_(base11) { polymorphic(); ... };
- struct_(base12) { polymorphic(); ... };
- struct_(base1) {
- inherit(base11);
- inherit(base12);
- polymorphic();
- ...
- };
- struct_(base2) { polymorphic(); ... };
- struct_(base3) { polymorphic(); ... };
- struct_(structType) {
- inherit(base1);
- inherit(base2);
- vbInherit(base3);
- polymorphic();
- ...
- };
-
- struct_vbc(structType) { // casing struct
- vbh_decl(structType);
- vb_decl(base3);
- };
-
-
-
- -----------------------------------------------------
- | |
- | |
- | ---------------------------- |
- | | | |
- | | ---------------------- | |
- | | | | | |
- | | | -------------- | | |
- | | | | | | | |
- | | | | base11 | | | |
- | | | |(descendanT)|-->| | |
- | | | -------------- | | |
- | | | | | |
- | | | -------------- | | |
- | | | | | | | |
- | | | | base12 | | | |
- | | | |(descendanT)|-->| | |
- | | | -------------- | | |
- | | | | | |
- | | | | | |
- | | | base1 | | |
- | | | (descendanT)|->| |
- | | ---------------------- | |
- | | | |
- | | | |
- | | -------------- | |
- | | | | | |
- | | | base2 | | |
- | | |(descendanT)|----->| |
- | | -------------- | |
- | | | |
- | | (host) | |
- | | structType | |
- | | | -------------- |
- | | (base3BasE)|--->| | |
- | | | | base3 | |
- | | |<---|(descendanT)| |
- | ---------------------------- -------------- |
- | |
- | structType casing struct |
- | |
- -----------------------------------------------------
-
-
-
- If structType were inherited by a derivedStructType
- then base3BasE would still point to base3 except base3
- would be in the derivedStructType casing struct and
- base3.descendanT would point to the derivedStructType
- host.
-
- I snuck in multiple inheritance and virtual base
- classes on you here. This contrived structure
- hierarchy appears in cop.hfm and cop.cfm with detailed
- comments explaining the COP implementation strategy.
- Instead of trying to explain multiple inheritance with
- various level vFt overrides here, go ahead and jump in
- by cloning cop.hfm and cop.cfm and follow the comments.
- The only way it is going to stick in your mind anyway
- is to get your hands dirty. Also described in the
- cop.?fm files are static class members, C++ template
- emulation, operator overloading conventions, and many
- subjects only briefly touch upon in this documentation.
-
- Your questions, comments, and suggestions are always
- welcome. Happy OOPing!
-
- John
- CIS 73757,2233
- (703) 759-3838
-
-
- ----------------end-of-author's-documentation---------------
-
- Software Library Information:
-
- This disk copy provided as a service of
-
- Public (software) Library
-
- We are not the authors of this program, nor are we associated
- with the author in any way other than as a distributor of the
- program in accordance with the author's terms of distribution.
-
- Please direct shareware payments and specific questions about
- this program to the author of the program, whose name appears
- elsewhere in this documentation. If you have trouble getting
- in touch with the author, we will do whatever we can to help
- you with your questions. All programs have been tested and do
- run. To report problems, please use the form that is in the
- file PROBLEM.DOC on many of our disks or in other written for-
- mat with screen printouts, if possible. PsL cannot debug pro-
- programs over the telephone, though we can answer questions.
-
- Disks in the PsL are updated monthly, so if you did not get
- this disk directly from the PsL, you should be aware that the
- files in this set may no longer be the current versions. Also,
- if you got this disk from another vendor and are having prob-
- lems, be aware that some files may have become corrupted or
- lost by that vendor. Get a current, working disk from PsL.
-
- For a copy of the latest monthly software library newsletter
- and a list of the 4,000+ disks in the library, call or write
-
- Public (software) Library
- P.O.Box 35705 - F
- Houston, TX 77235-5705
-
- 1-800-2424-PSL
- MC/Visa/AmEx/Discover
-
- Outside of U.S. or in Texas
- or for general information,
- Call 1-713-524-6394
-
-